package org.budo.graph.config;

import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.budo.graph.druid.spring.aop.interceptor.BudoGraphDruidSpringAopInterceptor;
import org.budo.graph.druid.spring.web.interceptor.BudoGraphDruidSpringWebInterceptor;
import org.budo.graph.service.BudoGraphService;
import org.budo.graph.service.impl.SpringBudoGraphServiceImpl;
import org.budo.graph.service.impl.WriteDumpScriptServiceImpl;
import org.budo.graph.service.impl.WriteHealthzScriptServiceImpl;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.SystemUtil;
import org.budo.support.spring.aop.advisor.BudoAopPointcutAdvisor;
import org.budo.support.spring.bean.factory.support.BeanBuilder;
import org.budo.support.spring.context.aware.BudoApplicationContextAware;
import org.budo.support.spring.util.SpringUtil;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.handler.MappedInterceptor;
import redis.clients.jedis.JedisPool;

import java.util.List;

/**
 * @author lmw
 * @see org.springframework.web.servlet.handler.MappedInterceptor
 * @see org.budo.spring.cache.interceptor.config.BudoSpringEhCacheInterceptorConfigurer
 * @see org.springframework.web.servlet.config.InterceptorsBeanDefinitionParser
 * @see com.alibaba.druid.spring.boot.autoconfigure.stat.DruidSpringAopConfiguration
 * @see com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
 * @see com.alibaba.dubbo.config.spring.schema.DubboBeanDefinitionParser
 */
@Slf4j
@Getter
@Setter
@Accessors(chain = true)
public class BudoGraphConfigurer implements BeanDefinitionRegistryPostProcessor {
    private static final String REGISTRY_CONFIG_ID = "registryConfig";

    private static final String SPRING_WEB_INTERCEPTOR_ID = "budoGraphDruidSpringWebInterceptor";

    private static final String BUDO_GRAPH_ADVISOR_ID = "budo-graph-advisor";

    private static final String BUDO_GRAPH_ASPECTJ_ID = "budo-graph-aspectj";

    private static final String APPLICATION_CONTEXT_AWARE_ID = "budoApplicationContextAware";

    private static final String AOP_INTERCEPTOR_ID = "budoGraphDruidSpringAopInterceptor";

    private static final String APPLICATION_CONFIG_ID = "applicationConfig";

    private static final String REGISTRY_SERVICE_REFERENCE_CONFIG_ID = "registryServiceReferenceConfig";

    private static final String BUDO_GRAPH_SERVICE_ID = "budoGraphService";

    private static final String SERVICE_CONFIG_ID = "budoGraphServiceConfig";

    private static final String PROVIDER_CONFIG_ID = "budoGraph-providerConfig";

    private static final String HANDLER_INTERCEPTOR_TYPE = "org.springframework.web.servlet.HandlerInterceptor";

    public static final String REGISTRY_SERVICE_TYPE = ReflectUtil.hasClass("org.apache.dubbo.registry.RegistryService") //
            ? "org.apache.dubbo.registry.RegistryService" //
            : "com.alibaba.dubbo.registry.RegistryService";

    public static final String REFERENCE_BEAN_TYPE = ReflectUtil.hasClass("org.apache.dubbo.config.spring.ReferenceBean") //
            ? "org.apache.dubbo.config.spring.ReferenceBean" //
            : "com.alibaba.dubbo.config.spring.ReferenceBean";

    public static final String SERVICE_BEAN_TYPE = ReflectUtil.hasClass("com.alibaba.dubbo.config.spring.ServiceBean") //
            ? "com.alibaba.dubbo.config.spring.ServiceBean" //
            : "org.apache.dubbo.config.spring.ServiceBean";

    public static final String URL_TYPE = ReflectUtil.hasClass("org.apache.dubbo.common.URL") //
            ? "org.apache.dubbo.common.URL" //
            : "com.alibaba.dubbo.common.URL";

    public static final String NOTIFY_LISTENER_TYPE = ReflectUtil.hasClass("org.apache.dubbo.registry.NotifyListener") //
            ? "org.apache.dubbo.registry.NotifyListener" //
            : "com.alibaba.dubbo.registry.NotifyListener";

    public static final String APPLICATION_CONFIG_TYPE = ReflectUtil.hasClass("org.apache.dubbo.config.ApplicationConfig") //
            ? "org.apache.dubbo.config.ApplicationConfig" //
            : "com.alibaba.dubbo.config.ApplicationConfig";

    // execution(* com.xyz.service..*.*(..))
    private String aopPatterns;

    private JedisPool jedisPool;

    private String jedisPoolBeanName;

    private String redisHost;

    private Integer redisPort = 6379;

    private String redisPassword;

    // spring.dubbo.registry.address
    private String dubboRegistryAddress;

    // spring.dubbo.application.name
    private String dubboApplicationName = dubboApplicationName();

    private Boolean referenceDubboRegistry;

    public static String dubboApplicationName() {
        String name = SystemUtil.getEnvOrProperty("user.dir", "dubboApplicationName");

        char[] chars = name.toCharArray();
        StringBuffer stringBuffer = new StringBuffer();
        for (char ch : chars) {
            if (Character.isDigit(ch) || Character.isLetter(ch) || ch == '-' || ch == '_' || ch == '.') {
                stringBuffer.append(ch);
            }
        }

        return stringBuffer.toString();
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        log.info("#20 postProcessBeanFactory, beanFactory=" + ObjectUtils.identityToString(beanFactory));
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        log.info("#25 postProcessBeanDefinitionRegistry, registry=" + ObjectUtils.identityToString(registry));

        this.registryBudoApplicationContextAware(registry);

        this.registryBudoGraphWebInterceptor(registry);

        this.registryBudoGraphAopInterceptor(registry);
        this.registryBudoGraphAopAdvisor(registry);

        // Dubbo 组件
        this.registryDubboComponents(registry);

        this.registerBudoGraphService(registry);
        this.registryBudoGraphDubboServiceConfig(registry); // 在 registryDubboRegistryConfig 后面

        // 健康检查脚本
        this.writeHealthzScript(registry);

        // Dump脚本
        this.writeDumpScript(registry);
    }

    private void writeDumpScript(BeanDefinitionRegistry registry) {
        log.warn("#142 writeDumpScript " + ObjectUtils.identityToString(registry));

        new BeanBuilder() //
                .type(WriteDumpScriptServiceImpl.class) //
                .registerTo(registry);
    }

    private void writeHealthzScript(BeanDefinitionRegistry registry) {
        log.warn("#150 writeHealthzScript " + ObjectUtils.identityToString(registry));

        new BeanBuilder() //
                .type(WriteHealthzScriptServiceImpl.class) //
                .registerTo(registry);
    }

    private void registryDubboComponents(BeanDefinitionRegistry registry) {
        if (!ReflectUtil.hasClass(REGISTRY_SERVICE_TYPE)) {
            log.warn("#103 registryDubboComponents return, no dubbo in classpath, registry=" + ObjectUtils.identityToString(registry));
            return;
        }

        this.registryDubboApplicationConfig(registry);

        this.registryDubboRegistryConfig(registry);

        this.registryDubboProviderConfig(registry);

        // springboot 项目 可能报错, zk 值未替换
        log.info("#117 registryDubboComponents, referenceDubboRegistry=" + this.getReferenceDubboRegistry());

        if (null == this.getReferenceDubboRegistry() || this.getReferenceDubboRegistry()) {
            this.registryDubboRegistryReference(registry);
        }
    }

    private void registryDubboProviderConfig(BeanDefinitionRegistry registry) {
        List<BeanDefinition> providerConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, ProviderConfig.class.getName());
        if (null != providerConfigDefinitions && !providerConfigDefinitions.isEmpty()) {
            log.info("#132 registryDubboProviderConfig=" + providerConfigDefinitions);
            return;
        }

        List<BeanDefinition> protocolConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, ProtocolConfig.class.getName());
        if (null != protocolConfigDefinitions && !protocolConfigDefinitions.isEmpty()) {
            log.info("#139 protocolConfigDefinitions=" + protocolConfigDefinitions);
            return;
        }

        new BeanBuilder().id(PROVIDER_CONFIG_ID) //
                .type(ProviderConfig.class) //
                .property("port", "-1") //
                .registerTo(registry);
    }

    private void registryDubboRegistryConfig(BeanDefinitionRegistry registry) {
        List<BeanDefinition> registryConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, RegistryConfig.class.getName());
        if (null != registryConfigDefinitions && !registryConfigDefinitions.isEmpty()) {
            log.info("#80 registryConfigDefinitions=" + registryConfigDefinitions);
            return;
        }

        if (null == this.getDubboRegistryAddress()) {
            log.info("#91 dubboRegistryAddress=" + this.getDubboRegistryAddress());
            return;
        }

        new BeanBuilder().id(REGISTRY_CONFIG_ID) //
                .type(RegistryConfig.class) //
                .property("protocol", "zookeeper") //
                .property("address", this.getDubboRegistryAddress()) //
                .registerTo(registry);
    }

    private void registryBudoGraphDubboServiceConfig(BeanDefinitionRegistry registry) {
        if (!ReflectUtil.hasClass(REGISTRY_SERVICE_TYPE)) {
            log.warn("#138 registryBudoGraphDubboServiceConfig return, no dubbo in classpath, registry=" + ObjectUtils.identityToString(registry));
            return;
        }

        List<BeanDefinition> registryConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, RegistryConfig.class.getName());
        if (null == registryConfigDefinitions || registryConfigDefinitions.isEmpty()) {
            log.info("#105 registryConfigDefinitions=" + registryConfigDefinitions);
            return;
        }

        new BeanBuilder().id(SERVICE_CONFIG_ID) //
                .type(SERVICE_BEAN_TYPE) //
                .property("interface", BudoGraphService.class.getName()) //
                .ref("ref", BUDO_GRAPH_SERVICE_ID) //
                .registerTo(registry);
    }

    private void registryDubboRegistryReference(BeanDefinitionRegistry registry) {
        log.info("#161 registryDubboRegistryReference, registry=" + ObjectUtils.identityToString(registry));

        new BeanBuilder().id(REGISTRY_SERVICE_REFERENCE_CONFIG_ID) //
                .type(REFERENCE_BEAN_TYPE) //
                .property("interface", REGISTRY_SERVICE_TYPE) //
                .registerTo(registry);
    }

    private void registryDubboApplicationConfig(BeanDefinitionRegistry registry) {
        Class applicationConfig = ReflectUtil.classForName(APPLICATION_CONFIG_TYPE);
        List<BeanDefinition> applicationConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, APPLICATION_CONFIG_TYPE);
        if (null != applicationConfigDefinitions && !applicationConfigDefinitions.isEmpty()) {
            log.info("#85 applicationConfigDefinitions=" + applicationConfigDefinitions);
            return;
        }

        new BeanBuilder().id(APPLICATION_CONFIG_ID) //
                .type(applicationConfig) //
                .property("name", this.getDubboApplicationName()) //
                .registerTo(registry);
    }

    private void registryBudoApplicationContextAware(BeanDefinitionRegistry registry) {
        List<BeanDefinition> applicationContextAwareDefinitions = SpringUtil.getBeanDefinitionListByType(registry, BudoApplicationContextAware.class.getName());
        if (null != applicationContextAwareDefinitions && !applicationContextAwareDefinitions.isEmpty()) {
            log.info("#139 applicationContextAwareDefinitions=" + applicationContextAwareDefinitions);
            return;
        }

        new BeanBuilder().id(APPLICATION_CONTEXT_AWARE_ID) //
                .type(BudoApplicationContextAware.class) //
                .registerTo(registry);
    }

    private void registryBudoGraphAopInterceptor(BeanDefinitionRegistry registry) {
        List<BeanDefinition> applicationConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, BudoGraphDruidSpringAopInterceptor.class.getName());
        if (null != applicationConfigDefinitions && !applicationConfigDefinitions.isEmpty()) {
            log.info("#85 applicationConfigDefinitions=" + applicationConfigDefinitions);
            return;
        }

        new BeanBuilder().id(AOP_INTERCEPTOR_ID) //
                .type(BudoGraphDruidSpringAopInterceptor.class) //
                .registerTo(registry);
    }

    private void registryBudoGraphAopAdvisor(BeanDefinitionRegistry registry) {
        BeanDefinition advisorBean = SpringUtil.getAdvisorBeanDefinitionByAdviceType(registry, BudoGraphDruidSpringAopInterceptor.class.getName());
        if (null != advisorBean) {
            log.info("#96 registryBudoGraphAopAdvisor return, advisorBean=" + advisorBean);
            return;
        }

        // 通过表达式配置Aop
        if (null != this.getAopPatterns() && !this.getAopPatterns().trim().isEmpty()) {
            new BeanBuilder().id(BUDO_GRAPH_ASPECTJ_ID) //
                    .type(AspectJExpressionPointcutAdvisor.class) //
                    .property("expression", this.getAopPatterns()) //
                    .ref("advice", AOP_INTERCEPTOR_ID)//
                    .registerTo(registry);
            return;
        }

        // 自动Aop
        new BeanBuilder().id(BUDO_GRAPH_ADVISOR_ID) //
                .type(BudoAopPointcutAdvisor.class) //
                .ref("advice", AOP_INTERCEPTOR_ID)//
                .registerTo(registry);
    }

    /**
     * TODO 判断已存在
     *
     * @see org.springframework.web.servlet.config.InterceptorsBeanDefinitionParser
     */
    private void registryBudoGraphWebInterceptor(BeanDefinitionRegistry registry) {
        if (!ReflectUtil.hasClass(HANDLER_INTERCEPTOR_TYPE)) {
            log.error("#206 ClassNotFound org.springframework.web.servlet.HandlerInterceptor");
            return;
        }

        new BeanBuilder().id(SPRING_WEB_INTERCEPTOR_ID) //
                .type(MappedInterceptor.class) //
                .constructorArg(null) // includePatterns
                .constructorArg(new BudoGraphDruidSpringWebInterceptor()) // interceptor
                .registerTo(registry);
    }

    private void registerBudoGraphService(BeanDefinitionRegistry registry) {
        List<BeanDefinition> budoGraphServiceDefinitions = SpringUtil.getBeanDefinitionListByType(registry, SpringBudoGraphServiceImpl.class.getName());
        if (null != budoGraphServiceDefinitions && !budoGraphServiceDefinitions.isEmpty()) {
            log.info("#126 budoGraphServiceDefinitions=" + budoGraphServiceDefinitions);
            return;
        }

        BeanBuilder budoGraphService = new BeanBuilder() //
                .id(BUDO_GRAPH_SERVICE_ID) //
                .type(SpringBudoGraphServiceImpl.class);
        if (null != this.getJedisPool()) {
            budoGraphService.property("jedisPool", this.getJedisPool());
        } else if (null != this.getJedisPoolBeanName()) {
            budoGraphService.ref("jedisPool", this.getJedisPoolBeanName());
        } else if (null != this.getRedisHost()) {
            budoGraphService.property("redisHost", this.getRedisHost());
            budoGraphService.property("redisPort", this.getRedisPort());
            budoGraphService.property("redisPassword", this.getRedisPassword());
        } else {
            log.error("#231 jedisPool=" + this.getJedisPool() + ", jedisPoolBeanName=" + this.getJedisPoolBeanName() + ", redisHost=" + this.getRedisHost());
        }

        budoGraphService.registerTo(registry);
    }
}
